﻿using System;
using System.Collections;
using System.IO;
using System.Linq;
using FlightToolkit.SimProp;
using Microsoft.VisualStudio.TestTools.UnitTesting;

// TODO: Test the following as well:
// - More Negative test cases
// - Single and multiple cardinality overrides
// - Localization
// - BasedOn functionality from Flight
// - Serializing all the various forms of properties (Enum, Float, Double, Float3, Filetime)
// - Serializing all the various forms of properties as attributes
// - Property setters\getters for different types
// - PropertySet: AddChildSet\AddChildProperty
// - PropertySet: RemoveChild*
// - Default values
// - Special cases for bugs in default Flight\FSX propdefs

namespace FlightToolkit.UnitTest
{
    [TestClass]
    public class SimPropFSXTests
    {
    }

    [TestClass]
    public class SimPropTests
    {
        private const string FlightSymbolPath = "SimPropSymbols\\Flight";
        private const string FlightBinaryTestFilePath = "TestFiles\\Flight\\TestFile1.spb";
        private const string FlightXmlTestFilePath = "TestFiles\\Flight\\TestFile1.xml";
        private const string FlightBinaryTestFile2Path = "TestFiles\\Flight\\TestFile2.spb";
        private const string FlightXmlTestFile2Path = "TestFiles\\Flight\\TestFile2.xml";

        private const string FSXSymbolPath = "SimPropSymbols\\FSX";
        private const string FSXBinaryTestFilePath = "TestFiles\\FSX\\TestFile1.spb";
        private const string FSXXmlTestFilePath = "TestFiles\\FSX\\TestFile1.xml";
        private const string FSXBinaryTestFile2Path = "TestFiles\\FSX\\TestFile2.spb";
        private const string FSXBinaryTestFile3Path = "TestFiles\\FSX\\TestFile3.spb";
        private const string FSXXmlTestFile3Path = "TestFiles\\FSX\\TestFile3.xml";

        private PropertySet CreateFlightTestDocument()
        {
            var symbols = Symbols.Load(null);
            var doc = PropertySet.CreateDocument(symbols);

            PropertySet metaSet = doc.CreateChildSet("Asset.Metadata");
            metaSet.CreateChildProperty("id").String = "{BAC56B2E-1D13-4C51-A4CC-5B06E573D3C7}";
            doc.CreateChildProperty("Asset.Metadata\\Type").Guid = Guid.Parse("{843497CD-59A5-44BD-B0D7-59366D9D5931}");
            metaSet.CreateChildProperty("Tag").String = "Tag1";
            metaSet.CreateChildProperty("Tag").String = "Tag2";

            return doc;
        }

        private PropertySet CreateFSXTestDocument()
        {
            var symbols = Symbols.Load(SimPropTests.FSXSymbolPath);
            var doc = PropertySet.CreateDocument(symbols);

            var seasonalModelsSet = doc.CreateChildSet("Autogen.SeasonalModels");
            var modelEntrySet = seasonalModelsSet.CreateChildSet("ModelEntry");
            modelEntrySet.CreateChildProperty("id").String = "{79FC6129-A185-4512-AA84-807906CCE053}";
            modelEntrySet.CreateChildProperty("MinScale").Float[0] = 0.8f;
            modelEntrySet.CreateChildProperty("MaxScale").Float[0] = 1.2f;
            var entry1Set = modelEntrySet.CreateChildSet("SeasonEntry");
            entry1Set.CreateChildProperty("ModelGuid").Guid = Guid.Parse("{57623A6E-753D-4FC6-996A-81181F5E4500}");
            var entry2Set = modelEntrySet.CreateChildSet("SeasonEntry");
            entry2Set.CreateChildProperty("Season").Int[0] = 1;
            entry2Set.CreateChildProperty("ModelGuid").Guid = Guid.Parse("{9EA22AD7-1221-4D6B-A8DE-F81980435B00}");

            var groupingsSet = doc.CreateChildSet("Autogen.AutogenGroupings");

            var groupingSet = groupingsSet.CreateChildSet("Grouping");
            groupingSet.CreateChildProperty("id").String = "{5952AF7C-3FF8-4115-9849-A75D6AFDF26B}";
            var groupEntrySet = groupingSet.CreateChildSet("ReferenceEntry");
            groupEntrySet.CreateChildProperty("TargetGuid").Guid = Guid.Parse("{79FC6129-A185-4512-AA84-807906CCE053}");
            groupEntrySet.CreateChildProperty("RelativeWeight").Float[0] = 1.0f;

            var regionSet = groupingsSet.CreateChildSet("Regionalization");
            regionSet.CreateChildProperty("id").String = "{029CC516-5444-4316-933E-3095E364C1A7}";
            var regionDescSet = regionSet.CreateChildSet("RegionDescription");
            regionDescSet.CreateChildProperty("RegionId").Int[0] = -1;
            var regionEntrySet = regionDescSet.CreateChildSet("ReferenceEntry");
            regionEntrySet.CreateChildProperty("TargetGuid").Guid = Guid.Parse("{5952AF7C-3FF8-4115-9849-A75D6AFDF26B}");
            regionEntrySet.CreateChildProperty("RelativeWeight").Float[0] = 1.0f;

            return doc;
        }

        private void CompareMemoryStreamToFile(MemoryStream stream, string file)
        {
            var actualData = File.ReadAllBytes(file);
            var testData = new byte[stream.Length];
            Array.Copy(stream.GetBuffer(), testData, testData.Length);
            if (!StructuralComparisons.StructuralEqualityComparer.Equals(actualData, testData))
            {
                throw new Exception();
            }
        }

        [TestMethod]
        public void TestFlightSymbolLoad()
        {
            var symbols = Symbols.Load(SimPropTests.FlightSymbolPath);
        }

        [TestMethod]
        public void TestFSXSymbolLoad()
        {
            var symbols = Symbols.Load(SimPropTests.FSXSymbolPath);
        }

        [TestMethod]
        public void TestBinaryRead()
        {
            var symbols = Symbols.Load(null);
            var doc = PropertySet.Load(symbols, SimPropTests.FlightBinaryTestFilePath);

            var metadata = doc.GetChildSet("Asset.Metadata");
            if (metadata == null)
            {
                throw new Exception();
            }

            var id = metadata.GetChildProperty("id");
            if (id == null)
            {
                throw new Exception();
            }

            var id2 = doc.GetChildProperty("Asset.Metadata\\id");
            if (id2 == null)
            {
                throw new Exception();
            }

            var tags = doc.GetChildProperties("Asset.Metadata\\Tag");
            if (tags == null || tags.Count() != 2)
            {
                throw new Exception();
            }
        }

        [TestMethod]
        public void TestXMLRead()
        {
            var symbols = Symbols.Load(null);
            var doc = PropertySet.Load(symbols, SimPropTests.FlightXmlTestFilePath);

            var metadata = doc.GetChildSet("Asset.Metadata");
            if (metadata == null)
            {
                throw new Exception();
            }

            var id = metadata.GetChildProperty("id");
            if (id == null)
            {
                throw new Exception();
            }

            var id2 = doc.GetChildProperty("Asset.Metadata\\id");
            if (id2 == null)
            {
                throw new Exception();
            }

            var tags = doc.GetChildProperties("Asset.Metadata\\Tag");
            if (tags == null || tags.Count() != 2)
            {
                throw new Exception();
            }
        }

        [TestMethod]
        public void TestBinaryWrite()
        {
            var doc = CreateFlightTestDocument();

            using (var outputStream = new MemoryStream())
            {
                doc.Save(outputStream, Format.FlightBinary);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FlightBinaryTestFilePath);
            }
        }

        [TestMethod]
        public void TestXMLWrite()
        {
            var doc = CreateFlightTestDocument();

            using (var outputStream = new MemoryStream())
            {
                doc.Save(outputStream, Format.XML);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FlightXmlTestFilePath);
            }
        }

        [TestMethod]
        public void TestXMLToBinary()
        {
            using (var outputStream = new MemoryStream())
            {
                var symbols = Symbols.Load(null);
                var doc = PropertySet.Load(symbols, SimPropTests.FlightXmlTestFilePath);
                doc.Save(outputStream, Format.FlightBinary);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FlightBinaryTestFilePath);
            }
        }

        [TestMethod]
        public void TestBinaryToXML()
        {
            using (var outputStream = new MemoryStream())
            {
                var symbols = Symbols.Load(null);
                var doc = PropertySet.Load(symbols, SimPropTests.FlightBinaryTestFilePath);
                doc.Save(outputStream, Format.XML);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FlightXmlTestFilePath);
            }
        }

        [TestMethod]
        public void TestFSXXMLWrite()
        {
            var doc = CreateFSXTestDocument();

            using (var outputStream = new MemoryStream())
            {
                doc.Save(outputStream, Format.XML);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FSXXmlTestFilePath);
            }
        }

        [TestMethod]
        public void TestFSXBinaryWrite()
        {
            var doc = CreateFSXTestDocument();

            using (var outputStream = new MemoryStream())
            {
                doc.Save(outputStream, Format.FSXBinary);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FSXBinaryTestFilePath);
            }
        }

        [TestMethod]
        public void TestFSXBinaryRead()
        {
            var symbols = Symbols.Load(SimPropTests.FSXSymbolPath);
            var doc = PropertySet.Load(symbols, SimPropTests.FSXBinaryTestFile2Path);
        }

        [TestMethod]
        public void TestFSXBinaryRead2()
        {
            var symbols = Symbols.Load(SimPropTests.FSXSymbolPath);
            var doc = PropertySet.Load(symbols, SimPropTests.FSXBinaryTestFile3Path);
            using (var outputStream = new MemoryStream())
            {
                doc.Save(outputStream, Format.XML);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FSXXmlTestFile3Path);
            }
        }

        [TestMethod]
        public void TestFSXBinaryWrite2()
        {
            var symbols = Symbols.Load(SimPropTests.FSXSymbolPath);
            var doc = PropertySet.Load(symbols, SimPropTests.FSXXmlTestFile3Path);
            using (var outputStream = new MemoryStream())
            {
                doc.Save(outputStream, Format.FSXBinary);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FSXBinaryTestFile3Path);
            }
        }

        [TestMethod]
        public void NegativeTestFSXBinaryRead()
        {
            // Read FSX file with Flight symbols
            try
            {
                var symbols = Symbols.Load(null);
                var doc = PropertySet.Load(symbols, SimPropTests.FSXBinaryTestFile2Path);

                throw new Exception("Should have failed");
            }
            catch (InvalidDataException)
            {
                // Expected failure
            }
        }

        [TestMethod]
        public void NegativeTestCreateMultipleSingleCardinalityProperty()
        {
            // Try to create multiple of the same property that is set to single cardinality
            try
            {
                var symbols = Symbols.Load(null);
                var doc = PropertySet.CreateDocument(symbols);

                PropertySet metaSet = doc.CreateChildSet("Asset.Metadata");
                metaSet.CreateChildProperty("id").String = "{BAC56B2E-1D13-4C51-A4CC-5B06E573D3C7}";
                metaSet.CreateChildProperty("id").String = "{E0AA9072-24C9-4538-AE6D-115F493433E3}";

                throw new Exception("Should have failed");
            }
            catch (InvalidOperationException)
            {
                // Expected failure
            }
        }

        [TestMethod]
        public void NegativeTestCreateMultipleSingleCardinalitySet()
        {
            // Try to create multiple of the same set that is set to single cardinality
            try
            {
                var symbols = Symbols.Load(null);
                var doc = PropertySet.CreateDocument(symbols);

                PropertySet mat1Set = doc.CreateChildSet("FlexMaterial.Material");
                PropertySet mat2Set = doc.CreateChildSet("FlexMaterial.Material");

                throw new Exception("Should have failed");
            }
            catch (InvalidOperationException)
            {
                // Expected failure
            }
        }

        [TestMethod]
        public void NegativeTestCreateInvalidProperty()
        {
            // Try to create a property that is not allow in a set
            try
            {
                var symbols = Symbols.Load(null);
                var doc = PropertySet.CreateDocument(symbols);

                PropertySet metaSet = doc.CreateChildSet("Asset.Metadata");
                var submeta = metaSet.CreateChildSet("Metadata");

                throw new Exception("Should have failed");
            }
            catch (InvalidOperationException)
            {
                // Expected failure
            }
        }

        [TestMethod]
        public void TestUnrestrictiveSet()
        {
            var symbols = Symbols.Load(null);
            var doc = PropertySet.CreateDocument(symbols);

            PropertySet propertiesSet = doc.CreateChildSet("FlexMaterial.Material\\Properties");
            propertiesSet.CreateChildProperty("SkyCylinderParameters0").Float[3] = 1;
            propertiesSet.CreateChildProperty("SourceTexture0TexelOffset").Float[1] = 1;
            propertiesSet.CreateChildProperty("HermiteSurfaceMatrices").Float[15] = 1;
            propertiesSet.CreateChildProperty("HermiteSurfaceMatrices").Float[15] = 1;
            propertiesSet.CreateChildProperty("TerrainMaterialIsReflectionPass").Int[0] = 1;

            propertiesSet.CreateChildProperty("Latitude").String = "Test";
            propertiesSet.CreateChildProperty("Level").Int[0] = -5;
            propertiesSet.CreateChildProperty("WorldPosition").Double[2] = 100.0;
            propertiesSet.CreateChildProperty("Orientation").Float[2] = 100;
            propertiesSet.CreateChildProperty("version").Int[1] = 1;

            using (var outputStream = new MemoryStream())
            {
                doc.Save(outputStream, Format.FlightBinary);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FlightBinaryTestFile2Path);
            }

            using (var outputStream = new MemoryStream())
            {
                doc.Save(outputStream, Format.XML);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FlightXmlTestFile2Path);
            }
        }

        [TestMethod]
        public void TestRoundTripUnrestrictiveSet()
        {
            var symbols = Symbols.Load(null);
            var doc = PropertySet.Load(symbols, SimPropTests.FlightXmlTestFile2Path);

            // Altitude is saved out to xml as feet, but in binary it is represented in meters.
            // Due to the floating point errors in converting the double to a string and back,
            // this can result in some rounding errors.  Do a fuzzy equals against 100m, and then
            // set it to exactly 100 before saving to binary again so we make sure we get the same
            // binary file.
            var worldPosProp = doc.GetChildProperty("FlexMaterial.Material\\Properties\\WorldPosition");
            if (Math.Abs(worldPosProp.Double[2] - 100) > 0.0000001)
            {
                // Some of the data didn't get converted properly.
                throw new Exception();
            }

            worldPosProp.Double[2] = 100;

            doc.Save("TestFile2.spb", Format.FlightBinary);
            doc.Save("TestFile2.xml", Format.XML);

            using (var outputStream = new MemoryStream())
            {
                doc.Save(outputStream, Format.FlightBinary);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FlightBinaryTestFile2Path);
            }

            var doc2 = PropertySet.Load(symbols, SimPropTests.FlightBinaryTestFile2Path);

            using (var outputStream = new MemoryStream())
            {
                doc.Save(outputStream, Format.XML);

                CompareMemoryStreamToFile(outputStream, SimPropTests.FlightXmlTestFile2Path);
            }
        }
    }
}
